package j.s.code.jsnippets.serial;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;

public class RunSerial {
	/**
	 * 机房占用5位
	 */
	private final long workNoBit = 5L;
	/**
	 * 机器编号占用5位
	 */
	private final long machineNoBit = 5L;
	/**
	 * 序列号占用12位
	 */
	private final long sequenceBit = 12L;

	private final long timeStemp = 41L;

	private final long timeStempMax = -1L^(-1L << timeStemp);

	/**
	 * sequenceMax 序列最大值
	 */
	private final long sequenceMask = -1L^(-1L << sequenceBit);
	/**
	 * machineNoMax 最大值
	 */
	private final long machineNoMax = -1L^(-1L << machineNoBit);
	/**
	 * workNoMax 最大值
	 */
	private final long workNoMax = -1L^(-1L<<workNoBit);

	private long lastTime=-1L;

	/**
	 * 序列号
	 */
	private long sequence = 0L;

	/**
	 * 起始时间
	 */
	private long startTime = 1539159134486L;
	/**
	 * 获取序列号
	 * 
	 * @param workNo    机房编号
	 * @param machineNo 机器编号
	 * @return long类型占用64位 规定： 时间戳占用41位 时间戳是指 当前时间-指定时间 机房占用5位 2的5次方 最多32个机房
	 *         机器编号占用5位 每一个机房最多32机器
	 */
	public synchronized long  getSerial(long workNo, long machineNo) throws Exception {
		if (workNo > workNoMax || workNo <0 ){
			throw new IllegalArgumentException(String.format("机房编号%d不能大于%d或者小于0",workNo,workNoMax));
		}
		if (machineNo > machineNoMax || machineNo <0 ){
			throw new IllegalArgumentException(String.format("机房编号%d不能大于%d或者小于0",machineNo,machineNoMax));
		}
		//获取当前时间
		long currentTime = timeGen();
		if(currentTime<lastTime){
			throw new Exception(String.format("获取系统时间非法currentTime=%d,lastTime=%d,currentTime-lastTime=%d", currentTime,lastTime,currentTime-lastTime));
		}
		//与当前时间相等
		if(currentTime==lastTime){
			//System.out.println(String.format("当前sequence：%d",sequence));
			//看是否越界 如果越界返回0 说明在这个时间中已经达到最大值
			sequence = (sequence+1) & sequenceMask;
			//System.out.println(String.format("当前sequence：%d",sequence));
			if(sequence==0L){
				//重新获取时间值
				currentTime = newCurrentTime(lastTime);
			}
		}else{
			//System.out.println(String.format("当前当前sequence需要归0"));
			//System.out.println(String.format("当前sequence：%d",sequence));
			//说明在新的时间里重新开始序列
			sequence = 0L;
			//System.out.println(String.format("当前sequence：%d",sequence));
		}
		lastTime = currentTime;

		//时间戳占用41位
		long times = (currentTime-startTime) << (this.workNoBit+this.machineNoBit+this.sequenceBit);
		//System.out.println(String.format("times二进制表示方式：%s",Long.toBinaryString(times)));
		long workNo_ = workNo << (this.sequenceBit+this.machineNoBit);
		//System.out.println(String.format("workNo_二进制表示方式：%s",Long.toBinaryString(workNo_)));
		long machineNo_ =  machineNo << this.sequenceBit;
		//System.out.println(String.format("machineNo_二进制表示方式：%s",Long.toBinaryString(machineNo_)));
		//System.out.println(String.format("sequence二进制表示方式：%s",Long.toBinaryString(sequence)));
		long id = times|workNo_|machineNo_|sequence;
		//System.out.println(String.format("id二进制表示方式：%s",Long.toBinaryString(id)));
		return id;
	}

	/**
	 * 获取新的时间值
	 * 
	 * @param lastTime 最近使用的时间值
	 * @return 新的时间值
	 */
	private long newCurrentTime(final long lastTime) {
		long newTime = timeGen();
		// 直到获取到最新时间
		while (newTime - lastTime <= 0) {
			newTime = timeGen();
		}
		return newTime;
	}

	/**
	 * 获取时间值
	 * 
	 * @return
	 */
	private long timeGen() {
		return System.currentTimeMillis();
	}

	public String paresSerialToString(long id) {
		String id2Str = "";
		long sequence_ = id & sequenceMask;
		long machine = (id & (this.machineNoMax<<this.sequenceBit))>>(this.sequenceBit);

		long workNo_ = (id & (this.workNoMax<<(this.sequenceBit+this.machineNoBit)))>>(this.sequenceBit+this.machineNoBit);

		long time = (id & (this.timeStempMax << (this.workNoBit+this.machineNoBit+this.sequenceBit)))>>(this.workNoBit+this.machineNoBit+this.sequenceBit);
		String id_ = String.format("序列id转换：时间 %d,机房：%d, 机器 %d, 序号：%d",time,workNo_, machine, sequence_);
		String id_s = String.format("序列id转换：时间 %s,机房：%s, 机器 %s, 序号：%s",Long.toBinaryString(time),Long.toBinaryString(workNo_),Long.toBinaryString(machine) , Long.toBinaryString(sequence_));
		System.out.println(id_);
		System.out.println(id_s);
		//时间转化
		String famat = "YYYYMMddHHmmssSSS";
		LocalDateTime localDateTime =  getDateTimeOfTimestamp(time+startTime);
		String dateStr = getDateTimeAsString(localDateTime,famat);
		System.out.println(String.format("时间转化String : %s", dateStr));
		id2Str = String.format("%s%02d%02d%04d", dateStr,workNo_,machine,sequence_);
		System.out.println(String.format("解析id数据：%s", id2Str));
		return id2Str;
	}
	/**
	 * 把毫秒数转化成当地时间
	 * @param timestamp
	 * @return
	 */
	public LocalDateTime getDateTimeOfTimestamp(long timestamp) {
		Instant instant = Instant.ofEpochMilli(timestamp);
		ZoneId zone = ZoneId.systemDefault();
		return LocalDateTime.ofInstant(instant, zone);
	}
	public  String getDateTimeAsString(LocalDateTime localDateTime, String format) {
		DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
		return localDateTime.format(formatter);
	}
	public Date localDateTime2Date( LocalDateTime localDateTime){        
		ZoneId zoneId = ZoneId.systemDefault();        
		ZonedDateTime zdt = localDateTime.atZone(zoneId);      
		Date date = Date.from(zdt.toInstant());        
		System.out.println(date.toString());
		return date;
	}


}
